# comun: file include processor
#
# This is a tool to help preprocess comun source files as related to the file
# incude directive. As pure comun doesn't support file I/O and so can only take
# a single input stream of characters, in cases when a source code consists of
# multiple source files we have to merge them into one input stream. This tool
# helps with that. It works in following way:
#
# Feed all source files to the input of this utility, concatenated in the
# following format:
#
# <filename1><newline><filecontent1><zero><filename2><newline>...
#
# The first file will be considered the main file. The utility will output a
# structure (NOT the file itself!) of the final file as lines, each in format:
#
# <filename> <space> <startpos> <space> <endpos>
#
# This will says which parts of which files and in what order have to be
# concatenated to produce the final file.
#
# NOTE: making the utility output the whole final file could be done too of
# course, but it would require a lot of RAM to hold the whole file contents. We
# output only the file structure in hopes other tools will be able to make the
# final file more efficiently.
#
# Basic principles: we process the input and construct one big str in TE 8; this
# str contains the file names along with symbols (start/end of file, file
# include) and positions, we then print the string out while recursively
# printing out the "include" parts.

STATE_FILENAME:        0 . # reading filename
STATE_CONTENT:         1 . # reading file content
STATE_CONTENT_COMMENT: 2 . # reading file content (inside comment)
STATE_CONTENT_STRING:  3 . # reading file content (inside str lit.)
STATE_CONTENT_TILDE:   4 . # reading file content, read tilde
STATE_CONTENT_INCLUDE: 5 . # reading file content, read include filename

SYMBOL_EOF:            1 . # marks end of file
SYMBOL_SOF:            2 . # marks start of file
SYMBOL_SOF_DONE:       3 . # marks start of already included file
SYMBOL_INCLUDE:        4 . # marks start of file include
SYMBOL_SPACE:          5 . # holds place for spaces
SYMBOL_BLOCK_SEP:      6 . # separates blocks

~state        # state machine state

~8
  ~str1:2048  # the big str
  ~str2:0     # smaller tmp str, will be pointed to main stack
  ~ptr:0
  ~ptr2:0
  ~initAddr:0 # initial stack top

  $0>str2
  $str1>0
  $str2>ptr
  $0>initAddr
~0

~32
  ~pos1 # helper vars
  ~pos2
~0

# arg: char
# ret:
# Pushes a single char to str1.
pushChar:
  ~8 0 ~0 >8
.

# arg: 3n
# ret:
# Converts a number to string which will be pushed to str1.
pushNum:
  ~32
    0 ><
    @@
      $0 10 % "0" + ><
      10 /

      $0 0 = ?
        !@
      .
    .
    ^

    @'
      ~8 0 ~32 >8
    .
    ^
  ~0
.

# Pushes one whole file range block represented as string to str1.
pushBlock:
  pushStr2
  SYMBOL_SPACE pushChar
  ~32 $pos1 pushNum ~0
  SYMBOL_SPACE pushChar
  ~32 $pos2 $pos1 - pushNum ~0
  SYMBOL_BLOCK_SEP pushChar
.

# Pushes str2 to str1.
pushStr2:
  ~8
    $str2>ptr
    
    1 @
      $ptr $>ptr
      $ptr
    .
  ~0
.

# Helper that shifts ptr and increments pos.
shiftPtr:
  ~8 $>ptr ~32 ++ ~0
.

# ret: 3n
# Seeks position of a file within the main str, returns its position.
seekFile:
  ~32
    1 seekPos
    1
  ~0

  ~8
    0 # comparing?

    @@
      $ptr !! ? ~32 ^ -1 ~8 !@ .

      ?' # comparing?
        $ptr2 !! $ptr 0 ~0 SYMBOL_SPACE >8 ~8 = & ?
          1 @ # go back to filename start 
            ~32 -- ~8
            $<ptr
            $ptr 0 ~0 SYMBOL_SOF >8 ~8 !=
          .

          !@
        .

        $ptr $ptr2 != ?
          ^ 0
        .

        $>ptr2
      .

      $ptr 0 ~0 SYMBOL_SOF >8 ~8 = ?  # TODO: make fun
        ^ 1
        $str2>ptr2
      ;
      $ptr 0 ~0 SYMBOL_SPACE >8 ~8 = ?
        ^ 0
      . .

      shiftPtr
    .

    ^
  ~0  
.

# art: 3pos
# ret:
# Seeks ptr to given position within str1.
seekPos:
  ~8 $initAddr>ptr ~0

  ~32
    @' # go to the passed position
      ~8 $>ptr ~32
      --
    .
    ^  
  ~0
.

# arg: 3strPos
# Prints all blocks of a file, potentially recursively printing included files.
printFile:
  ~32 $0 seekPos ~0 # we'll keep the position below, for recursion

  0 ~8 $ptr >0 ~0 SYMBOL_SOF_DONE = ? # file already included?
    ~32 ^ ~0
    !.
  .

  ~8 0 ~0 SYMBOL_SOF_DONE >8 ~8 $:ptr ~0 # mark done

  shiftPtr

  ~8
    @@ # go char by char
      $ptr 0 ~0 SYMBOL_EOF >8 ~8 = ? !@ .

      $ptr 0 ~0 SYMBOL_INCLUDE >8 ~8 = ?
        # load the included file name into str2
        $str2>ptr2

        @@ # copy the filename to str2
          shiftPtr
          $ptr 0 ~0 SYMBOL_BLOCK_SEP >8 ~8 = ?
            !@
          .

          $ptr $:ptr2
          $>ptr2
        .

        0 $:ptr2

        seekFile
        10 ->

        ~32
          $0 -1 != ?
            printFile
          ;
            ^
          .
        ~8

        ~32 $0 seekPos ~8
      .

      $ptr 0 ~0 SYMBOL_BLOCK_SEP >8 ~8 != ?
        $ptr 0 ~0 SYMBOL_SPACE >8 ~8 != $ptr " " ?? ->
      .

      shiftPtr
    .
  ~0

  ~32 ^ ~0

  10 ->
.

1 @ # read all input
  <-
  $state STATE_FILENAME = ?
    $0 10 = ? # newline?
      ^

      SYMBOL_SOF pushChar
      STATE_CONTENT $:state

      ~32
        0 $:pos1
        0 $:pos2
      ~0
    ;
      pushChar ~8 $:ptr ~0
    .
      
    ~8 $>ptr 0 $:ptr ~0
  ; # STATE_CONTENT
    ~32 $pos2 ++ $:pos2 ~0

    $0 0 = ?
      pushBlock
      SYMBOL_EOF pushChar
      ~8 $str2>ptr 0 $:ptr ~0
      STATE_FILENAME $:state
    ;
      $0 "#" = $state STATE_CONTENT = & ?
        STATE_CONTENT_COMMENT $:state
      ;
      $0 "#" = $1 10 = | $state STATE_CONTENT_COMMENT = & ?
        STATE_CONTENT $:state
      ;
      $0 34 = $state STATE_CONTENT = & ?
        STATE_CONTENT_STRING $:state
      ;
      $0 34 = $state STATE_CONTENT_STRING = & ?
        STATE_CONTENT $:state
      ;
      $0 "~" = $state STATE_CONTENT = & ?
        STATE_CONTENT_TILDE $:state
      ;
      $state STATE_CONTENT_TILDE = ?
        $0 34 = ?
          ~32 $pos2 2 - $:pos2 ~0 # don't include the two previous chars
          pushBlock
          ~32 $pos2 2 + $:pos2 ~0
          STATE_CONTENT_INCLUDE $:state
          SYMBOL_INCLUDE pushChar
        ;
          STATE_CONTENT $:state
        .
      ;
      $state STATE_CONTENT_INCLUDE = ?
        $0 34 != ?
          $0 pushChar
        ;
          ~32 $pos2 ++ $:pos1 ~0
          STATE_CONTENT $:state
          SYMBOL_BLOCK_SEP pushChar
        .
      . . . . . . .
    .


    ^
  .

  <?
.

0 pushChar # terminate str1

~32 1 printFile ~0
